package core;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CommandHistory extends ModelObject
{
	/**
	 * 
	 */
	private static final long serialVersionUID = 315714133562209783L;

	/** stores the history of commands executed */
	private List<Command> history;
	
	private Map<Product, List<Item>> items;
	private List<Product> productsEffected;
	
	/** index into list for undo and redo 
	 * points to the index just past the last executed command*/
	private int undoRedoIndex;

	public CommandHistory()
	{
		history = new ArrayList<Command>();
		productsEffected = new ArrayList<Product>();
		items = new HashMap<Product, List<Item>>();
		undoRedoIndex = 0;
	}

	/**
	 * executes the command and puts it into the history
	 * @param command the Command object to be executed
	 */
	public Item execute(Command command)
	{
		// clear all possible redo's once we execute a new command
		int size = history.size();
		int i;
		for(i = size-1; i >= undoRedoIndex;i--)
		{
			history.remove(i);
		}
		history.add(command);
		addItemExecuteExtra(command);
		undoRedoIndex++;
		return command.execute();
	}
	
	/**
	 * undoes the last command executed
	 */
	public Item undo()
	{
		Command undo = history.get(undoRedoIndex-1);
		Item item = undo.undo();
		undoRedoIndex--;
		//Take it off the map of products.
		addItemUndoExtra(undo, item);
		return item;
	}
	
	/**
	 * re-does the last command that was undone
	 */
	public Item redo()
	{
		Command redo = history.get(undoRedoIndex);
		addItemExecuteExtra(redo);
		Item item = redo.execute();
		undoRedoIndex++;
		try{
			redo = history.get(undoRedoIndex);
		}catch(IndexOutOfBoundsException e){
			return item;
		}
		
		if(redo.getGroupNumber() > 0)
			redo();
		return item;
	}
	
	/** 
	 * determines whether doing an undo operation is legal
	 * @return true if undo is valid, false otherwise
	 */
	public boolean canUndo()
	{
		if(history.size()>0 && undoRedoIndex > 0)
			return true;
		return false;
	}
	
	/** 
	 * determines whether doing a redo operation is legal
	 * @return true if redo is valid, false otherwise
	 */
	public boolean canRedo()
	{
		if(undoRedoIndex < history.size())
			return true;
		return false;
	}
	
	/**
	 * clears the history buffer
	 */
	public void clearHistory()
	{
		history.clear();
		items.clear();
		productsEffected.clear();
		undoRedoIndex = 0;
	}
	
	public Map<Product, List<Item>> getMap()
    {
    	return items;
    }
    
    public List<Product> getList()
    {
    	return productsEffected;
    }
    
    public ProductContainer getRemovedItemPC()
    {
    	RemoveItemCommand command = (RemoveItemCommand) history.get(undoRedoIndex-1);
    	return command.getContainer();  	
    }
    
    private void addItemExecuteExtra(Command command)
    {
    	if(command instanceof AddItemCommand)
		{
			Item item = command.getItem();
			Product p = item.getProduct();
			List<Item> toAdd;
			if (items.containsKey(p))
			{
				toAdd = items.get(p);
			}
			else
			{
				toAdd = new ArrayList<Item>();
				items.put(p, toAdd);
				productsEffected.add(p);
			}
			toAdd.add(item);
		}
    }
    
    private void addItemUndoExtra(Command undo, Item item)
    {
    	if(undo instanceof AddItemCommand)
		{
			List<Item> list = items.get(item.getProduct());
			list.remove(item);
			if(list.isEmpty())
			{
				items.remove(item.getProduct());
				productsEffected.remove(item.getProduct());
			}
			if(undo.getGroupNumber() != 0)
				undo();
		}
    }
}
